package main

import (
	//"CCServer.com/CCModules/CCCommon"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"github.com/gin-gonic/gin"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	GET = iota
	POST
)

type HandlerItem struct {
	HandlerMethod int
	HandlerName   string
	HandlerFunc   gin.HandlerFunc
	HandlerData   interface{}
	VerifyToken   bool
}

type GroupHandler struct {
	GroupName string
	Item      []HandlerItem
}

func CustomData(data interface{}) gin.HandlerFunc {
	return func(c *gin.Context) {
		//CCCommon.Logger.Infof("CustomData.data[%v]...", data)
		if data != nil {
			c.Set("CustomData", data)
		}
		c.Next()
	}
}

func GetCustomData(c *gin.Context, dataName string) interface{} {
	var CustomData interface{}
	var exists bool
	if CustomData, exists = c.Get(dataName); !exists {
		fmt.Println("GetCustomData.CustomData.nil")
		return nil
	}
	return CustomData
}

func StartupReverseProxy(addr string, group GroupHandler) {
	router := gin.Default()

	router.Use(cors())

	// we just only support GET/POST method
	// Cool.Cat
	v := router.Group(group.GroupName)
	for _, item := range group.Item {
		switch item.HandlerMethod {
		case GET:
			{
				v.GET(item.HandlerName, tokenVerified(item.VerifyToken), CustomData(item.HandlerData), item.HandlerFunc)
				break
			}
		case POST:
			{
				v.POST(item.HandlerName, tokenVerified(item.VerifyToken), CustomData(item.HandlerData), item.HandlerFunc)
				break
			}
		}
	}

	if err := router.Run(addr); err != nil {
		fmt.Println("StartupReverseProxy.Run.err[%v]", err)
		return
	}
	fmt.Println("StartupReverseProxy.Run.OK")
}

func ReverseProxy(w http.ResponseWriter, req *http.Request, remote *url.URL) {
	bTls := false
	if strings.Compare(remote.Scheme, "https") == 0 {
		bTls = true
	}

	proxy := httputil.NewSingleHostReverseProxy(remote)
	if bTls {
		proxy.Transport = &http.Transport{
			DialTLS: func(nw string, addr string) (net.Conn, error) {
				c, err := net.DialTimeout(nw, addr, time.Second*time.Duration(10))
				if err != nil {
					return nil, err
				}

				// check tls fmt
				host, _, err := net.SplitHostPort(addr)
				if err != nil {
					return nil, err
				}
				cfg := &tls.Config{ServerName: host}
				tlsConn := tls.Client(c, cfg)
				if err := tlsConn.Handshake(); err != nil {
					_ = c.Close()
					return nil, err
				}

				cs := tlsConn.ConnectionState()
				cert := cs.PeerCertificates[0]

				// Verify cert name
				if err = cert.VerifyHostname(host); err != nil {
					fmt.Println("ReverseProxy.Host[%v].VerifyHostname.err[%v]", remote.Host, err)
					return nil, err
				}
				fmt.Println("ReverseProxy.Host[%v].VerifyHostname.Subject[%v]", remote.Host, cert.Subject)

				return c, nil
			},
			ResponseHeaderTimeout: time.Second * time.Duration(10),
			//TLSClientConfig:       tls,
		}
	}
	proxy.ModifyResponse = func(resp *http.Response) error {
		// we can modify response header before send to client
		// Cool.Cat
		return nil
	}

	director := proxy.Director
	proxy.Director = func(req *http.Request) {
		// we can modify request header before send to beckend
		// Cool.Cat
		director(req)
		req.Header.Set("X-Forwarded-Host", req.RemoteAddr)
		req.Host = req.URL.Host
	}

	proxy.ServeHTTP(w, req)
}

func cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method

		origin := c.Request.Header.Get("Origin")
		var headerKeys []string
		for k, _ := range c.Request.Header {
			headerKeys = append(headerKeys, k)
		}

		//CCCommon.Logger.Infof("cors.method[%v].headerKeys[%v]...", method, headerKeys)

		headerStr := strings.Join(headerKeys, ", ")
		if headerStr != "" {
			headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, tk, Uid, up2ws, Upgrade, Connection, Sec-WebSocket-Accept, Sec-WebSocket-Protocol, %s", headerStr)
		} else {
			headerStr = "access-control-allow-origin, access-control-allow-headers, tk, Uid, up2ws, Upgrade, Connection, Sec-WebSocket-Accept, Sec-WebSocket-Protocol"
		}

		if origin != "" {
			//c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
			c.Header("Access-Control-Allow-Origin", "*")
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
			c.Header("Access-Control-Allow-Headers", headerStr)
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, Authorization,tk, Uid, up2ws, Upgrade, Connection, Sec-WebSocket-Accept, Sec-WebSocket-Protocol")
			//c.Header("Access-Control-Allow-Credentials", "true")
			//c.Set("content-type", "application/json")
		}

		// allow OPTIONS method when client send custom header to us
		// so client can send GET/POST again with real custom header.
		// Cool.Cat
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}

		c.Next()
	}
}


// ParseToken 解析token
func ParseToken(token string) (string, error) {
	if len(token) == 0 {
		return "", fmt.Errorf("decrypt.token.nil")
	}
	//var ciphertext = make([]byte, len(token))
	token = strings.Replace(token, "-", "/", -1)
	token = strings.Replace(token, "_", "+", -1)
	ciphertext, err := base64.StdEncoding.DecodeString(token)
	//ciphertext, err := base64.URLEncoding.DecodeString(token)
	if err != nil {
		return "", fmt.Errorf("decrypt.base64.decode.invalid")
	}

	//playerToken, _, err := Decrypt(myKey, ciphertext, false)
	playerToken := ciphertext
	if err != nil {
		return "", fmt.Errorf("parse Token err:%v", err)
	}

	realToken := fmt.Sprintf("%s", playerToken)

	splits := strings.Split(realToken, "@")
	if len(splits) != 2 {
		return "", fmt.Errorf("ParseToken err no @ at text")
	}

	timestamp, err := strconv.Atoi(splits[1])
	if err != nil {
		return "", fmt.Errorf("ParseToken err:%v", err, timestamp)
	}

	//now := TimeStamp()
	//
	//if (now - timestamp) > (myTimeExpired / myTimeInterval) {
	//	return "", fmt.Errorf("ParseToken token has been expired")
	//}

	return splits[0], nil
}


func tokenVerified(VerifyToken bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("Verified", false)
		c.Set("tk", "")
		c.Set("Uid", "")

		if VerifyToken == false {
			c.Next()
			return
		}

		tk := c.Query("t")
		if tk == "" {
			c.JSON(http.StatusOK, gin.H{
				"Code":    -1,
				"Message": "Not allowed",
			})
			c.Abort()
			return
		}

		// verify token
		// Cool.Cat
		var Uid = ""
		var err error
		if Uid, err = ParseToken(tk); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Code":    -2,
				"Message": "Kidding me?",
			})
			c.Abort()
			return
		}

		c.Set("Verified", true)
		c.Set("tk", tk)
		c.Set("Uid", Uid)
		c.Next()
	}
}

func HandlerAuth(c *gin.Context) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("HandlerAuth.recover.err[%v]", err)
		}
	}()
	fmt.Println("________________auth___")
}

func main(){
	StartupReverseProxy(":18002", GroupHandler{
		GroupName: "v1",
		Item: []HandlerItem{
			{
				HandlerMethod: GET,
				HandlerName:   "/oa/:uuid",
				HandlerFunc:   HandlerAuth,
				VerifyToken:   true,
			},
		},
	})

	defer func() {
		<- make(chan bool)
	}()
}